package org.fffd.l23o6.service.impl;

import io.github.lyc8503.spring.starter.incantation.exception.BizException;
import lombok.RequiredArgsConstructor;
import org.fffd.l23o6.dao.OrderDao;
import org.fffd.l23o6.dao.RouteDao;
import org.fffd.l23o6.dao.TrainDao;
import org.fffd.l23o6.dao.UserDao;
import org.fffd.l23o6.exception.BizError;
import org.fffd.l23o6.pojo.entity.OrderEntity;
import org.fffd.l23o6.pojo.entity.RouteEntity;
import org.fffd.l23o6.pojo.entity.TrainEntity;
import org.fffd.l23o6.pojo.entity.UserEntity;
import org.fffd.l23o6.pojo.enum_.OrderStatus;
import org.fffd.l23o6.pojo.enum_.TrainType;
import org.fffd.l23o6.pojo.vo.order.OrderVO;
import org.fffd.l23o6.service.OrderService;
import org.fffd.l23o6.util.strategy.payment.AlipayStrategy;
import org.fffd.l23o6.util.strategy.payment.PaymentStrategy;
import org.fffd.l23o6.util.strategy.payment.WechatPayStrategy;
import org.fffd.l23o6.util.strategy.train.GSeriesSeatStrategy;
import org.fffd.l23o6.util.strategy.train.KSeriesSeatStrategy;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
    private final OrderDao orderDao;
    private final UserDao userDao;
    private final TrainDao trainDao;
    private final RouteDao routeDao;

    //>= lower <upper
    private long[] discountLower = new long[]{1000, 3000, 10000, 50000};
    private long[] discountUpper = new long[]{3000, 10000, 50000, Long.MAX_VALUE};
    private BigDecimal[] discountPercent = new BigDecimal[]{
            new BigDecimal("0.001"), new BigDecimal("0.0015"),
            new BigDecimal("0.002"), new BigDecimal("0.0025")
    };
    private BigDecimal[] discountPrice = new BigDecimal[]{
            new BigDecimal("1"), new BigDecimal("4"),
            new BigDecimal("18"), new BigDecimal("118")
    };

    public Long createOrder(String username, Long trainId, Long fromStationId, Long toStationId, String seatType,
                            Long seatNumber) {
        Long userId = userDao.findByUsername(username).getId();
        TrainEntity train = trainDao.findById(trainId).get();
        RouteEntity route = routeDao.findById(train.getRouteId()).get();
        int startStationIndex = route.getStationIds().indexOf(fromStationId);
        int endStationIndex = route.getStationIds().indexOf(toStationId);
        String seat = null;
        switch (train.getTrainType()) {
            case HIGH_SPEED:
                seat = GSeriesSeatStrategy.INSTANCE.allocSeat(startStationIndex, endStationIndex,
                        GSeriesSeatStrategy.GSeriesSeatType.fromString(seatType), train.getSeats());
                break;
            case NORMAL_SPEED:
                seat = KSeriesSeatStrategy.INSTANCE.allocSeat(startStationIndex, endStationIndex,
                        KSeriesSeatStrategy.KSeriesSeatType.fromString(seatType), train.getSeats());
                break;
        }
        if (seat == null) {
            throw new BizException(BizError.OUT_OF_SEAT);
        }
        OrderEntity order = OrderEntity.builder().trainId(trainId).userId(userId).seat(seat)
                .status(OrderStatus.PENDING_PAYMENT).arrivalStationId(toStationId).departureStationId(fromStationId)
                .build();
        //更新updatedAt
        train.setUpdatedAt(null);// force it to update
        trainDao.save(train);
        orderDao.save(order);
        return order.getId();
    }

    public List<OrderVO> listOrders(String username) {
        Long userId = userDao.findByUsername(username).getId();
        List<OrderEntity> orders = orderDao.findByUserId(userId);
        orders.sort((o1, o2) -> o2.getId().compareTo(o1.getId()));
        return orders.stream().map(order -> {
            TrainEntity train = trainDao.findById(order.getTrainId()).get();
            RouteEntity route = routeDao.findById(train.getRouteId()).get();
            int startIndex = route.getStationIds().indexOf(order.getDepartureStationId());
            int endIndex = route.getStationIds().indexOf(order.getArrivalStationId());
            return OrderVO.builder().id(order.getId()).trainId(order.getTrainId())
                    .seat(order.getSeat()).status(order.getStatus().getText())
                    .createdAt(order.getCreatedAt())
                    .startStationId(order.getDepartureStationId())
                    .endStationId(order.getArrivalStationId())
                    .departureTime(train.getDepartureTimes().get(startIndex))
                    .arrivalTime(train.getArrivalTimes().get(endIndex))
                    .build();
        }).collect(Collectors.toList());
    }

    public OrderVO getOrder(Long id) {
        OrderEntity order = orderDao.findById(id).get();
        TrainEntity train = trainDao.findById(order.getTrainId()).get();
        RouteEntity route = routeDao.findById(train.getRouteId()).get();
        int startIndex = route.getStationIds().indexOf(order.getDepartureStationId());
        int endIndex = route.getStationIds().indexOf(order.getArrivalStationId());
        return OrderVO.builder().id(order.getId()).trainId(order.getTrainId())
                .seat(order.getSeat()).status(order.getStatus().getText())
                .createdAt(order.getCreatedAt())
                .startStationId(order.getDepartureStationId())
                .endStationId(order.getArrivalStationId())
                .departureTime(train.getDepartureTimes().get(startIndex))
                .arrivalTime(train.getArrivalTimes().get(endIndex))
                .build();
    }

    //类似于下面的pay
    public void cancelOrder(Long id, String paymentWay, String creditPay, int usedCredit) {
        OrderEntity order = orderDao.findById(id).get();

        if (order.getStatus() == OrderStatus.COMPLETED || order.getStatus() == OrderStatus.CANCELLED) {
            throw new BizException(BizError.ILLEAGAL_ORDER_STATUS);
        }

        // TODO: refund user's money and credits if needed
        UserEntity userEntity = userDao.findById(order.getUserId()).get();
        TrainEntity trainEntity = trainDao.findById(order.getTrainId()).get();
        Integer price = getPrice(order.getSeat(), trainEntity.getTrainType(), creditPay, userEntity.getCredit());

        PaymentStrategy paymentStrategy = null;
        switch (paymentWay) {
            case "1":
                paymentStrategy = AlipayStrategy.INSTANCE;
                break;
            case "2":
                paymentStrategy = WechatPayStrategy.INSTANCE;
                break;
            default:
                break;
        }

        if (paymentStrategy == null) paymentStrategy = AlipayStrategy.INSTANCE;
        paymentStrategy.refund(BigDecimal.valueOf(price));
        userEntity.setCredit(BigDecimal.valueOf(usedCredit));
        userDao.save(userEntity);
        order.setStatus(OrderStatus.CANCELLED);
        orderDao.save(order);
    }

    //paymentWay:1表示支付宝支付，2表示微信支付
    //creditPay:0表示不用积分抵现，1表示积分抵现
    public void payOrder(Long id, String paymentWay, String creditPay) {
        OrderEntity order = orderDao.findById(id).get();
        if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {
            throw new BizException(BizError.ILLEAGAL_ORDER_STATUS);
        }
        UserEntity userEntity = userDao.findById(order.getUserId()).get();
        TrainEntity trainEntity = trainDao.findById(order.getTrainId()).get();
        Integer price = getPrice(order.getSeat(), trainEntity.getTrainType(), creditPay, userEntity.getCredit());

        //修改：积分数等于付款金额，都是整数
        //支付的金额根据打折策略来计算
        // TODO: use payment strategy to pay!
        //需要分为微信支付和支付宝支付两种支付方式
        PaymentStrategy paymentStrategy = null;
        switch (paymentWay) {
            case "1":
                paymentStrategy = AlipayStrategy.INSTANCE;
                break;
            case "2":
                paymentStrategy = WechatPayStrategy.INSTANCE;
                break;
            default:
                break;
        }

        assert paymentStrategy != null;
        paymentStrategy.pay(id, BigDecimal.valueOf(price), order.getUserId());


        // TODO: update user's credits, so that user can get discount next time
        if (!creditPay.equals("0")) userEntity.setCredit(new BigDecimal(0));
        userEntity.setCredit(userEntity.getCredit().add(new BigDecimal(price)));
        order.setStatus(OrderStatus.PAID);
        orderDao.save(order);
    }

    // 修改：关于积分规则的修改，积分兑换使用分层系数，并一次性使用全部积分
    private Integer getPrice(String seat, TrainType trainType, String creditPay, BigDecimal credit) {
        Integer price = null;
        switch (trainType) {
            case HIGH_SPEED:
                price = GSeriesSeatStrategy.INSTANCE.getPrice(seat);
                break;
            case NORMAL_SPEED:
                price = KSeriesSeatStrategy.INSTANCE.getPrice(seat);
                break;
        }
        BigDecimal discount = new BigDecimal(0);
        for (int i = 0; i < discountLower.length; i++) {
            if (credit.longValue() >= discountLower[i] && credit.longValue() < discountUpper[i]) {
                discount = discountPrice[i];
                discount = new BigDecimal(price).subtract(discount).multiply(discountPercent[i]);
            }
        }
        BigDecimal resPrice = new BigDecimal(price).subtract(discount);

        switch (creditPay) {
            case "0":
                return price;
            case "1":
                return resPrice.intValue();
            default:
                return 0;
        }
    }
}